# Ameem Ahmed, Agyakwa Tenkorang, Nayiri Krzysztofowicz
# PyRTL ALU

import pyrtl
import random

# Constant used for bit-width
WIDTH = 32


# Define ALU instructions
instructions = {
    'add_inst': pyrtl.Const(0, bitwidth=WIDTH),
    'sub_inst': pyrtl.Const(1, bitwidth=WIDTH),
    'xor_inst': pyrtl.Const(2, bitwidth=WIDTH),
    'and_inst': pyrtl.Const(3, bitwidth=WIDTH),
    'or_inst': pyrtl.Const(4, bitwidth=WIDTH),
    'mult_inst': pyrtl.Const(5, bitwidth=WIDTH),
    'not_inst': pyrtl.Const(6, bitwidth=WIDTH)
}


# Define ALU structure
def alu(in1, in2, instruction):
    temp = pyrtl.WireVector(WIDTH, 'temp')
    with pyrtl.conditional_assignment:
        with instruction == instructions['add_inst']:
            temp |= in1 + in2
        with instruction == instructions['sub_inst']:
            temp |= in1 - in2
        with instruction == instructions['xor_inst']:
            temp |= in1 ^ in2
        with instruction == instructions['and_inst']:
            temp |= in1 & in2
        with instruction == instructions['or_inst']:
            temp |= in1 | in2
        with instruction == instructions['mult_inst']:
            temp |= in1 * in2
        with instruction == instructions['not_inst']:
            temp |= ~in2
        with pyrtl.otherwise:
            temp |= pyrtl.Const(0, bitwidth=WIDTH)
    return temp


# Instantiate input and output WireVectors
operand_1 = pyrtl.Input(WIDTH, 'operand_1')
operand_2 = pyrtl.Input(WIDTH, 'operand_2')
inst = pyrtl.Input(WIDTH, 'instruction')
result = pyrtl.Output(WIDTH, 'result')


# Connect the ALU output to the result WireVector
result <<= alu(operand_1, operand_2, inst)


# Instantiate the Simulation trace
sim_trace = pyrtl.SimulationTrace()
sim = pyrtl.Simulation(tracer=sim_trace)


# Run simulation for num_cycles
num_cycles = 15
for cycle in range(num_cycles):
    # Set inputs to random values for each cycle
    sim.step({
        'operand_1': random.randint(0, 2**WIDTH-1),
        'operand_2': random.randint(0, 2**WIDTH-1),
        'instruction': random.randint(0, 6)
    })


# Render the trace
sim_trace.render_trace()


# Output to a Verilog file named 'alu.v'
with open('alu.v', 'w') as v_file:
    pyrtl.OutputToVerilog(v_file)


# Exit Program
exit(0)
